package com.jprocms.module.cms.dal.dataobject.channel;

import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import com.jprocms.module.cms.dal.dataobject.model.ModelDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.enums.EnumCommonStatus;
import com.jprocms.module.cms.enums.EnumRecycle;
import com.jprocms.module.cms.framework.tree.TreeDo;
import com.jprocms.module.cms.framework.tree.TreePathUtil;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.ModelWrapper;
import com.jprocms.module.cms.web.IPageUrl;
import com.jprocms.module.cms.web.IUrl;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.Nullable;

import javax.validation.constraints.Pattern;
import java.util.*;
import java.util.stream.Collectors;

import static com.jprocms.module.cms.constant.CmsSysConstants.*;

/**
 * 栏目 DO
 *
 * @author jprocms
 */
@TableName(value = "cms_channel", autoResultMap = true)
@KeySequence("cms_channel_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChannelDO extends TreeDo implements IPageUrl, IUrl {

    /**
     * ID
     */
    @TableId
    private Long id;
    /**
     * 栏目模型ID
     */
    private Long modelId;
    /**
     * 内容模型ID
     */
    private Long contentModelId;
    /**
     * 站点ID
     */
    private Long siteId;
    /**
     * 父栏目ID
     */
    private Long parentId;
    /**
     * 栏目名称
     */
    private String name;
    /**
     * 访问路径
     */
    private String path;
    /**
     * 层级目录（父ID_ID拼接）
     */
    private String nodePath;
    /**
     * 工作流流程模型的最新部署的流程定义
     */
    private String processDefinitionId;
    /**
     * 排序
     */
    private Integer sort;
    /**
     * 列表是否显示
     */
    private Boolean listStatus;
    /**
     * 是否加入回收站（0-未加入  1-已加入）
     */
    private Boolean recycleStatus;
    /**
     * 是否允许投稿（0-不允许  1-允许）
     */
    private Boolean contributeStatus;
    /**
     * 是否开启索引查询
     */
    private Boolean searchStatus;
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 上级栏目
     */
    @Nullable
    @TableField(exist = false)
    private ChannelDO parent;

    @Nullable
    @TableField(exist = false)
    private SiteDO site;
    /**
     * 栏目模型
     */
    @JsonIncludeProperties({"id", "name"})
    @TableField(exist = false)
    private ModelDO channelModel = new ModelDO();

    /**
     * 栏目扩展信息
     */
    @JsonIgnore
    @TableField(exist = false)
    private ChannelExtDO channelExt = new ChannelExtDO();
    /**
     * 内容模型
     */
    @JsonIncludeProperties({"id", "name"})
    @TableField(exist = false)
    private ModelDO contentModel = new ModelDO();

    @TableField(exist = false)
    @JsonIncludeProperties({"id", "name", "url", "path", "channelModel", "contentModel"})
    private List<ChannelDO> children;

    public String getTxt() {
        return getChannelExt().getTxt();
    }

    public String getSeoTitle() {
        return getChannelExt().getSeoTitle();
    }

    public String getSeoDescription() {
        return getChannelExt().getSeoDescription();
    }

    public String getSeoKeyword() {
        return getChannelExt().getSeoKeyword();
    }

    /**
     * 自定义属性
     */
    @JsonIncludeProperties({"id", "name", "attrType", "attrValue"})
    @TableField(exist = false)
    private List<ChannelAttrDO> attrDos = new ArrayList<>();
    /**
     * 自定义属性转换map结构
     */
    @Nullable
    @TableField(exist = false)
    private Map<String, Object> customs;

    /**
     * 总访问量
     */
    private Long views;
    /**
     * 今年访问量
     */
    private Long yearViews;
    /**
     * 今日访问量
     */
    private Integer todayViews;

    public Map<String, Object> getCustoms() {
        if (customs == null) {
            ModelWrapper modelWrapper = new ModelWrapper(getChannelModel());
            customs = modelWrapper.assembleCustoms(getAttrDos());
        }
        return customs;
    }

    public void init() {
        setRecycleStatus(EnumCommonStatus.CLOSE.getStatus());
        setListStatus(Optional.ofNullable(getListStatus()).orElse(EnumCommonStatus.OPEN.getStatus()));
        setContributeStatus(Optional.ofNullable(getContributeStatus()).orElse(EnumCommonStatus.CLOSE.getStatus()));
        setSearchStatus(Optional.ofNullable(getSearchStatus()).orElse(EnumCommonStatus.OPEN.getStatus()));
        setViews(0L);
        setTodayViews(0);
        setYearViews(0L);
        setSort(10);
    }

    @Nullable
    @JsonIncludeProperties({"id", "name", "url"})
    public ChannelDO getParent() {
        return parent;
    }


    /**
     * 是否回收站状态
     *
     * @return
     */
    public boolean getRecycle() {
        return EnumRecycle.RECYCLE.equals(recycleStatus);
    }


    @JsonIncludeProperties({"id", "name", "url"})
    public List<ChannelDO> getNodeList() {
        LinkedList<ChannelDO> parents = new LinkedList<>();
        ChannelDO bean = this;
        while (bean != null) {
            parents.addFirst(bean);
            bean = bean.getParent();
        }
        return parents;
    }

    /**
     * 获取栏目可用工作流引擎,自身未配置则一直向上取取上级栏目的配置
     */
    @JsonIgnore
    public String getUseProcessDefinitionId() {
        if (StringUtils.isNotBlank(getProcessDefinitionId())) {
            return getProcessDefinitionId();
        }
        while (getParent() != null) {
            return getParent().getUseProcessDefinitionId();
        }
        return null;
    }

    /**
     * 父节点到自身栏目名称
     *
     * @return
     */
    public List<String> getNodeNames() {
        return getNodeList().stream().map(ChannelDO::getName).collect(Collectors.toList());
    }

    @Schema(description = "URL地址")
    public String getUrl() {
        return getUrl(1L);
    }

    @Override
    public Boolean getTargetBlank() {
        return getChannelExt().getLinkTarget();
    }

    @Override
    public String getUrl(long page) {
        return getSite().getOpenSiteStaticPage() ? getStaticUrl(page) : getDynamicUrl(page);
    }

    @Schema(description = "跳转链接")
    @Nullable
    @Pattern(regexp = "^(http|/).*$")
    public String getLinkUrl() {
        return getChannelExt().getOutLinkUrl();
    }

    @Schema(description = "动态URL地址")
    public String getDynamicUrl() {
        return getDynamicUrl(1);
    }

    @Override
    public String getDynamicUrl(long page) {
        //有外部链接或者是引用的内容返回外部链接或原文url
        if (StringUtils.isNotBlank(getLinkUrl())) {
            return getLinkUrl();
        }
        StringBuilder buff = new StringBuilder(getSite().getDynamicUrl());
        String prefix = CHANNEL_PREFIX;
        String channelPrefix = getSite().getGlobalConfigDO().getChannelUrlPrefix();
        if (StringUtils.isNotBlank(channelPrefix)) {
            prefix = channelPrefix;
        }
        buff.append(prefix).append(SPT).append(getPath());
        if (page > 1) {
            buff.append(SPT).append(page);
        }
        return buff.toString();
    }

    @JsonIgnore
    @Nullable
    public String getStaticFile() {
        return getChannelExt().getStaticFile();
    }

    public void setStaticFile(@Nullable String staticFile) {
        getChannelExt().setStaticFile(staticFile);
    }

    @JsonIgnore
    @Nullable
    public String getH5StaticFile() {
        return getChannelExt().getH5StaticFile();
    }

    public void setH5StaticFile(@Nullable String h5StaticFile) {
        getChannelExt().setH5StaticFile(h5StaticFile);
    }

    @JsonIgnore
    public String getStaticUrl() {
        return getStaticUrl(1);
    }


    public String getStaticUrl(long page) {
        if (StringUtils.isNotBlank(getLinkUrl())) {
            return getLinkUrl();
        }
        // 如果页数大于最大静态化页数，则使用动态页
        if (page > getSite().getSiteConfig().getStaticPage().getListMaxPageSize()) {
            return getDynamicUrl(page);
        }
        return CmsContextUtil.isPc() ? getNormalStaticUrl(page) : getMobileStaticUrl(page);
    }

    public String getNormalStaticUrl(long page) {
        return getSite().getPcStaticUrl(getChannelStaticFilePath(page));
    }

    public String getMobileStaticUrl(long page) {
        return getSite().getMobileStaticUrl(getChannelStaticFilePath(page));
    }

    private String getChannelStaticFilePath(long page) {
        return getSite().getChannelFilePath(getPath(), page);
    }

    public String getPcStaticPath(long page) {
        return getSite().getPcStaticPath(getChannelStaticFilePath(page));
    }

    public String getH5StaticPath(long page) {
        return getSite().getH5StaticPath(getChannelStaticFilePath(page));
    }


    /**
     * 获取模板路径（/default/channel/news.html）
     *
     * @return
     */
    @JsonIgnore
    public String getTpl() {
        String template = TPL_CHANNEL + SPT + getChannelTplAdapter();
        if (StringUtils.isBlank(template)) {
            template = TPL_CHANNEL + SPT + "news.html";
        }
        return getSite().joinTemplate(template);
    }

    /**
     * 获取模板路径 不带模板方案文件夹（/channel/news.html）
     *
     * @return
     */
    @Schema(description = "栏目模板")
    @Nullable
    public String getChannelTplAdapter() {
        return CmsContextUtil.isPc() ? getChannelExt().getTplPc() : getChannelExt().getTplMobile();
    }


    /**
     * 获取内容模板路径（/default/content/news.html）
     *
     * @return
     */
    @JsonIgnore
    public String getContentTpl() {
        String template = getContentTplAdapter();
        if (StringUtils.isBlank(template)) {
            template = "content";
        }
        return getSite().joinTemplate(template);
    }

    /**
     * 获取模板路径 不带模板方案文件夹（/content/news.html）
     *
     * @return
     */
    @Schema(description = "内容模板")
    @Nullable
    @JsonIgnore
    public String getContentTplAdapter() {
        return CmsContextUtil.isPc() ? getChannelExt().getContentTplPc() : getChannelExt().getContentTplMobile();
    }

    /**
     * 获取顶层栏目ID
     *
     * @return
     */
    public Long getTopId() {
        Long topId = TreePathUtil.getTopId(getPath());
        return topId == null ? getId() : topId;
    }

    @JsonIncludeProperties({"id", "name", "url"})
    public ChannelDO getTop() {
        LinkedList<ChannelDO> parents = new LinkedList<>();
        ChannelDO bean = this;
        while (bean != null) {
            parents.addFirst(bean);
            bean = bean.getParent();
        }
        return parents.get(0);
    }

    @JsonIgnore
    public String getNodePathPrefix() {
        return getNodePath() + "_";
    }

    @Override
    public String toString() {
        return "ChannelDO{id=" + getId() + ", name=" + getName() + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        ChannelDO channelDO = (ChannelDO) o;
        return Objects.equals(id, channelDO.id) && Objects.equals(modelId, channelDO.modelId) && Objects.equals(contentModelId, channelDO.contentModelId) && Objects.equals(siteId, channelDO.siteId) && Objects.equals(parentId, channelDO.parentId) && Objects.equals(name, channelDO.name) && Objects.equals(path, channelDO.path) && Objects.equals(nodePath, channelDO.nodePath) && Objects.equals(processDefinitionId, channelDO.processDefinitionId) && Objects.equals(sort, channelDO.sort) && Objects.equals(listStatus, channelDO.listStatus) && Objects.equals(recycleStatus, channelDO.recycleStatus) && Objects.equals(contributeStatus, channelDO.contributeStatus) && Objects.equals(searchStatus, channelDO.searchStatus) && Objects.equals(userId, channelDO.userId) && Objects.equals(views, channelDO.views) && Objects.equals(yearViews, channelDO.yearViews) && Objects.equals(todayViews, channelDO.todayViews);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), id, modelId, contentModelId, siteId, parentId, name, path, nodePath, processDefinitionId, sort, listStatus, recycleStatus, contributeStatus, searchStatus, userId, views, yearViews, todayViews);
    }
}
